#include <utils/err.h>
#include <seminix/cache.h>
#include <seminix/syscall.h>
#include <seminix/tcb.h>
#include <seminix/param.h>
#include <cap/frame.h>
#include <libseminix/types.h>

static struct kmem_cache *cap_frame_cache;
static struct kmem_cache *frame_cache;

static __init int frame_cap_init(void)
{
    cap_frame_cache = KMEM_CACHE(cap_frame, SLAB_PANIC);
    frame_cache = KMEM_CACHE(frame, SLAB_PANIC);

    return 0;
}
userver_initcall(frame_cap_init)

static cap_t *frame_cap_create(seminix_object_t *object)
{
    int ret, i;
    int count = object->frame.count;
    int order = object->frame.order;
    cap_frame_t *cap_frame;
    frame_t *frame;

    cap_frame = kmem_cache_alloc(cap_frame_cache, GFP_KERNEL | GFP_ZERO);
    if (!cap_frame)
        return ERR_PTR(-SERRNO_ENOMEM);

    ret = -SERRNO_ENOMEM;
    frame = kmem_cache_alloc(frame_cache, GFP_KERNEL);
    if (!frame)
        goto free_cap_frame;

    frame->page.pages = kcalloc(count, sizeof (struct page *), GFP_ZERO);
    if (!frame->page.pages)
        goto free_frame;
    for (i = 0; i < count; i++) {
        frame->page.pages[i] = alloc_pages(GFP_ZERO, order);
        if (!frame->page.pages[i])
            goto free_pages;
    }
    frame->page.nr_pages = count;
    frame->page.order = order;
    frame->frame_type = FRAMETYPE_PAGE;

    cap_frame->frame = frame;

    return CAP_REF(cap_frame);
free_pages:
    assert(i != count);
    for (i = 0; i < count; i++) {
        if (frame->page.pages[i]) {
            __free_pages(frame->page.pages[i], order);
            continue;
        }
        break;
    }
    kfree(frame->page.pages);
free_frame:
    kmem_cache_free(frame_cache, frame);
free_cap_frame:
    kmem_cache_free(cap_frame_cache, cap_frame);
    return ERR_PTR(ret);
}

static cap_t *frame_cap_dup(cap_t *cap)
{
    cap_frame_t *cap_frame;

    cap_frame = kmem_cache_alloc(cap_frame_cache, GFP_KERNEL | GFP_ZERO);
    if (!cap_frame)
        return ERR_PTR(-SERRNO_ENOMEM);

    cap_frame->frame = CAP_FRAME_PTR(cap)->frame;
    return CAP_REF(cap_frame);
}

static void frame_cap_revoke(cap_t *cap)
{
    kmem_cache_free(cap_frame_cache, CAP_FRAME_PTR(cap));
}

static void frame_cap_delete(cap_t *cap)
{
    cap_frame_t *cap_frame = CAP_FRAME_PTR(cap);
    frame_t *frame = cap_frame->frame;
    int count = frame->page.nr_pages;
    int order = frame->page.order;

    for (int i = 0; i < count; i++)
        __free_pages(frame->page.pages[i], order);
    kfree(frame->page.pages);
    kmem_cache_free(frame_cache, frame);
    kmem_cache_free(cap_frame_cache, cap_frame);
}

const struct cap_ops frame_cap_ops __ro_after_init = {
    .cap_create = frame_cap_create,
    .cap_dup = frame_cap_dup,
    .cap_revoke = frame_cap_revoke,
    .cap_delete = frame_cap_delete,
};
